<?php 
/**
 * QFrameWork
 *
 * @author: quyang
 * @date: 2010-2-1
 * @version: 1.0
 */
 
 /**
 * Router Class
 *
 * Parses URIs and determines routing
 */
Class Router {
    private $class = '';
    private $method = DEFAULT_METHOD;
	private $directory = '';
    private $default_controller = DEFAULT_CONTROLLER;
    private $uri_string;
    public $segments = array();
    
    public function __construct() {
        $this->_fetch_uri_string();
        // Is there a URI string? If not, the default controller will be shown.
        if ($this->uri_string == '') {
            if ($this->default_controller === FALSE) {
                throw new SystemError("Unable to determine what should be displayed. A default route has not been specified.");
            }
            
            if (strpos($this->default_controller, '/') !== FALSE) {
                $arr = explode('/', $this->default_controller);
                
                $this->set_class(end($arr));
                $this->set_method(DEFAULT_METHOD);
                $this->_set_request($arr);
            } else {
                $this->set_class($this->default_controller);
                $this->set_method(DEFAULT_METHOD);
                $this->_set_request(array($this->default_controller, DEFAULT_METHOD));
            }
            return;
        }
        // Compile the segments into an array
        $this->_explode_segments();
        $this->_set_request($this->segments);
        // Re-index the segment array so that it starts with 1 rather than 0
        array_unshift($this->segments, NULL);
        unset($this->segments[0]);
    }
    
    /**
     * Get the URI String
     *
     * @return	string
     */
    function _fetch_uri_string() {
        //如果是?形式的链接就直接拿走不客气啦
        if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '') {
            $this->uri_string = key($_GET);
            return;
        }
		/*
        //个别WebServer和FastCGI模式运行的PHP有可能拿不到PATH_INFO(如果没有手动配置的话)
        $path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
        if (trim($path, '/') != '' && $path != "/".SELF) {
            $this->uri_string = $path;
            return;
        }
        */
        //fastcgi_params中应该默认配置了QUERY_STRING
        $path = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
        if (trim($path, '/') != '') {
            $this->uri_string = $path;
            return;
        }
        // If the final attempt below doesn't work ,we've exhausted all our options...
        // the $this->uri_string will be ''
        $this->uri_string = $this->_parse_request_uri();
        
        return;
    }
    /**
     * Parse the REQUEST_URI
     *
     * Due to the way REQUEST_URI works it usually contains path info
     * that makes it unusable as URI data.  We'll trim off the unnecessary
     * data, hopefully arriving at a valid URI that we can use.
     *
     * @return	string
     */
    function _parse_request_uri() {
        if (!isset($_SERVER['REQUEST_URI']) OR $_SERVER['REQUEST_URI'] == '') {
            return '';
        }
        $request_uri = preg_replace("|/(.*)|", "\\1", str_replace("\\", "/", $_SERVER['REQUEST_URI']));
        
        if ($request_uri == '' OR $request_uri == SELF) {
            return '';
        }
        
        $fc_path = CORE_ROOT.SELF;
        if (strpos($request_uri, '?') !== FALSE) {
            $fc_path .= '?';
        }
        
        $parsed_uri = explode("/", $request_uri);
        
        $i = 0;
        foreach (explode("/", $fc_path) as $segment) {
            if (isset($parsed_uri[$i]) && $segment == $parsed_uri[$i]) {
                $i++;
            }
        }
        
        $parsed_uri = implode("/", array_slice($parsed_uri, $i));
        
        if ($parsed_uri != '') {
            $parsed_uri = '/'.$parsed_uri;
        }
        
        return $parsed_uri;
    }
    /**
     * Explode the URI Segments. The individual segments will
     * be stored in the $this->segments array.
     *
     * @return	void
     */
    function _explode_segments() {
        foreach (explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val) {
            // Filter segments for security
            $val = trim($this->_filter_uri($val));
            
            if ($val != '') {
                $this->segments[] = $val;
            }
        }
    }
    /**
     * Filter segments for malicious characters
     *
     * @param	string
     * @return	string
     */
    function _filter_uri($str) {
        // Convert programatic characters to entities
        $bad = array('$', '(', ')', '%28', '%29', '>', '<', '%3E', '%3C');
        $good = array('&#36;', '&#40;', '&#41;', '&#40;', '&#41;', '&#62;', '&#60;', '&#62;', '&#60;');
        
        return str_replace($bad, $good, $str);
    }
    /**
     * Set the class name
     *
     * @access	public
     * @param	string
     * @return	void
     */
    function set_class($class) {
        $this->class = $class;
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Fetch the current class
     *
     * @access	public
     * @return	string
     */
    function fetch_class() {
        return $this->class ;
    }
    
    // --------------------------------------------------------------------
    
    /**
     *  Set the method name
     *
     * @access	public
     * @param	string
     * @return	void
     */
    function set_method($method) {
        $this->method = $method;
    }
    
    // --------------------------------------------------------------------
    
    /**
     *  Fetch the current method
     *
     * @access	public
     * @return	string
     */
    function fetch_method() {
        if ($this->method == $this->fetch_class()) {
            return DEFAULT_METHOD;
        }
        
        return $this->method;
    }
    /**
     *  Set the directory name
     *
     * @access	public
     * @param	string
     * @return	void
     */
    function set_directory($dir) {
        $this->directory = $dir.DELIMITER;
    }
    
    // --------------------------------------------------------------------
    
    /**
     *  Fetch the sub-directory (if any) that contains the requested controller class
     *
     * @access	public
     * @return	string
     */
    function fetch_directory() {
        return $this->directory;
    }
    /**
     * Set the Route
     *
     * This function takes an array of URI segments as
     * input, and sets the current class/method
     *
     * @access	private
     * @param	array
     * @param	bool
     * @return	void
     */
    function _set_request($segments = array()) {
        $segments = $this->_validate_request($segments);
        
        if (count($segments) == 0) {
            return;
        }

        $this->set_class($segments[0]);
        
        if (isset($segments[1])) {
            $this->set_method($segments[1]);
        } else {
            // This lets the "routed" segment array identify that the default
            // index method is being used.
            $segments[1] = DEFAULT_METHOD;
        }
    }
    
    /**
     * Validates the supplied segments.  Attempts to determine the path to
     * the controller.
     *
     * @access	private
     * @param	array
     * @return	array
     */
    function _validate_request($segments) {
        // Does the requested controller exist in the root folder?
        if (file_exists(CORE_CONTROLLOR.$segments[0].EXT)) {
            return $segments;
        }
        // Is the controller in a sub-folder?
        if (is_dir(CORE_CONTROLLOR.$segments[0])) {
            // Set the directory and remove it from the segment array
            $this->set_directory($segments[0]);
            $segments = array_slice($segments, 1);
            
            if (count($segments) > 0) {
                // Does the requested controller exist in the sub-folder?
                if (!file_exists(CORE_CONTROLLOR.$this->fetch_directory().$segments[0].EXT)) {
                    throw new NotFound404();
                }
            } else {
                $this->set_class($this->default_controller);
                $this->set_method(DEFAULT_METHOD);
                
                // Does the default controller exist in the sub-folder?
                if (!file_exists(CORE_CONTROLLOR.$this->fetch_directory().$this->default_controller.EXT)) {
                    $this->directory = '';
                    return array();
                }
            }
            return $segments;
        }
        // Can't find the requested controller...
        throw new NotFound404();
    }
}
?>